1 /*
2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021
3 License:   [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License].
4 Authors: Marcelo S. N. Mancini
5 
6 	Copyright Marcelo S. N. Mancini 2018 - 2021.
7 Distributed under the CC BY-4.0 License.
8    (See accompanying file LICENSE.txt or copy at
9 	https://creativecommons.org/licenses/by/4.0/
10 */
11 module hip.hiprenderer.shader.shader;
12 public import hip.api.renderer.shadervar : ShaderVariablesLayout, ShaderVar;
13 public import hip.api.renderer.shadervar;
14 
15 import hip.api.data.commons:IReloadable;
16 import hip.api.renderer.texture;
17 import hip.math.matrix;
18 import hip.error.handler;
19 import hip.api.renderer.shadervar;
20 import hip.hiprenderer.shader;
21 import hip.hiprenderer.renderer;
22 import hip.util.file;
23 import hip.util.string:indexOf;
24 public import hip.api.renderer.shader;
25 
26 
27 
28 
29 
30 private __gshared ShaderProgram lastBoundShader;
31 
32 public class Shader : IReloadable
33 {
34     VertexShader vertexShader;
35     FragmentShader fragmentShader;
36     ShaderProgram shaderProgram;
37     ShaderVariablesLayout[string] layouts;
38     protected ShaderVariablesLayout defaultLayout;
39     //Optional
40     IShader shaderImpl;
41     string fragmentShaderPath;
42     string vertexShaderPath;
43 
44     protected string internalVertexSource;
45     protected string internalFragmentSource;
46     private bool isUseCall = false;
47 
48     this(IShader shaderImpl)
49     {
50         this.shaderImpl = shaderImpl;
51         vertexShader = shaderImpl.createVertexShader();
52         fragmentShader = shaderImpl.createFragmentShader();
53         shaderProgram = shaderImpl.createShaderProgram();
54     }
55     this(IShader shaderImpl, string vertexSource, string fragmentSource)
56     {
57         this(shaderImpl);
58         ShaderStatus status = loadShaders(vertexSource, fragmentSource);
59         if(status != ShaderStatus.SUCCESS)
60         {
61             import hip.console.log;
62             logln("Failed loading shaders");
63         }
64     }
65 
66     ShaderStatus loadShaders(string vertexShaderSource, string fragmentShaderSource, string shaderPath = "")
67     {
68         this.internalVertexSource = vertexShaderSource;
69         this.internalFragmentSource = fragmentShaderSource;
70 
71         vertexShaderPath = fragmentShaderPath = shaderProgram.name = shaderPath;
72         if(!shaderImpl.compileShader(vertexShader, vertexShaderSource))
73             return ShaderStatus.VERTEX_COMPILATION_ERROR;
74         if(!shaderImpl.compileShader(fragmentShader, fragmentShaderSource))
75             return ShaderStatus.FRAGMENT_COMPILATION_ERROR;
76         if(!shaderImpl.linkProgram(shaderProgram, vertexShader, fragmentShader))
77             return ShaderStatus.LINK_ERROR;
78         // deleteShaders();
79         return ShaderStatus.SUCCESS;
80     }
81 
82     ShaderStatus loadShadersFromFiles(string vertexShaderPath, string fragmentShaderPath)
83     {
84         this.vertexShaderPath = vertexShaderPath;
85         this.fragmentShaderPath = fragmentShaderPath;
86         return loadShaders(getFileContent(vertexShaderPath), getFileContent(fragmentShaderPath));
87     }
88 
89     ShaderStatus reloadShaders()
90     {
91         return loadShadersFromFiles(this.vertexShaderPath, this.fragmentShaderPath);
92     }
93 
94     void setVertexAttribute(uint layoutIndex, int valueAmount, uint dataType, bool normalize, uint stride, int offset)
95     {
96         shaderImpl.sendVertexAttribute(layoutIndex, valueAmount, dataType, normalize, stride, offset);
97     }
98 
99 
100     /**
101      * If validateData is true, it will compare if the data has changed for choosing whether it should or not
102      send to the GPU.
103      * Params:
104      *   name =
105      *   val =
106      *   validateData =
107      */
108     public void setVertexVar(T)(string name, T val, bool validateData = false)
109     {
110         ShaderVar* v = tryGetShaderVar(name, ShaderTypes.vertex);
111         if(v != null)
112         {
113             v.set(val, validateData);
114         }
115     }
116 
117     private ShaderVar* tryGetShaderVar(string name, ShaderTypes type)
118     {
119         import hip.util.conv:to;
120         bool isUnused;
121         ShaderVar* v = findByName(name, isUnused);
122 
123         if(v is null)
124         {
125             if(!isUnused)
126                 ErrorHandler.showWarningMessage("Shader " ~ type.to!string ~ " Var not set on shader loaded from '"~fragmentShaderPath~"'",
127                 "Could not find shader var with name "~name~
128                 ((layouts.length == 0) ?". Did you forget to addVarLayout on the shader?" :
129                 " Did you forget to add a layout namespace to the var name?")
130                 );
131             return null;
132         }
133         if(v.shaderType != type)
134         {
135             import hip.console.log;
136             logln = v.shaderType;
137             ErrorHandler.assertExit(false, "Variable named "~name~" must be from " ~ type.to!string ~ " Shader");
138         }
139         return v;
140     }
141     /**
142      * If validateData is true, it will compare if the data has changed for choosing whether it should or not
143      send to the GPU.
144      * Params:
145      *   name =
146      *   val =
147      *   validateData =
148      */
149     public void setFragmentVar(T)(string name, T val, bool validateData = false)
150     {
151         ShaderVar* v = tryGetShaderVar(name, ShaderTypes.fragment);
152         if(v != null)
153         {
154             if(v.isBlackboxed)
155             {
156                 if(shaderImpl.setShaderVar(v,shaderProgram, cast(void*)&val))
157                     v.isDirty = true;
158             }
159             else
160                 v.set(val, validateData);
161         }
162     }
163 
164     public void setFragmentVar(T)(ShaderVar* v, T val, bool validateData = false)
165     {
166         if(v.isBlackboxed)
167         {
168             if(shaderImpl.setShaderVar(v,shaderProgram, cast(void*)&val))
169                 v.isDirty = true;
170         }
171         else
172             v.set(val, validateData);
173     }
174 
175     protected ShaderVar* findByName(string name, out bool isUnused) @nogc
176     {
177         int accessorSeparatorIndex = name.indexOf(".");
178 
179         bool isDefault = accessorSeparatorIndex == -1;
180         if(isDefault)
181         {
182             ShaderVarLayout* sL = name in defaultLayout.variables;
183             if(sL !is null)
184                 return sL.sVar;
185             isUnused = defaultLayout.isUnused(name);
186         }
187         else
188         {
189             ShaderVariablesLayout* l = (name[0..accessorSeparatorIndex] in layouts);
190             if(l !is null)
191             {
192                 ShaderVarLayout* sL = name[accessorSeparatorIndex+1..$] in l.variables;
193                 if(sL !is null)
194                     return sL.sVar;
195                 isUnused = l.isUnused(name[accessorSeparatorIndex+1..$]);
196             }
197         }
198         return null;
199     }
200 
201     /** 
202      * This function is mostly used for debug information
203      * Returns: The Variable names.
204      */
205     private string[] getExistingVariableNames(ShaderTypes type) const
206     {
207         string[] ret;
208         foreach(k, v; layouts)
209         {
210             if(v.shaderType == type)
211             {
212                 ret~= v.variables.keys;
213             }
214         }
215         return ret;
216     }
217 
218     /**
219      * Use that instead of setVertexVar or setFragmentVar if you wish more performance.
220      * Params:
221      *   name = The name of the variable
222      * Returns: The Shader Variable reference
223      */
224     public ShaderVar* get(string name, ShaderTypes type)
225     {
226         ShaderVar* ret = tryGetShaderVar(name, type);
227         if(!ret)
228         {
229             import hip.util.string:join;
230             import hip.util.conv:to;
231             throw new Exception(
232                 "Could not find variable named '"~name~"'.\n\tDefault Layout: ["~this.defaultLayout.name~
233                 "].\n\tShader Path: "~ (type == ShaderTypes.fragment ? fragmentShaderPath : vertexShaderPath) ~
234                 "\\tnExisting Variables in shader type "~type.to!string~":\n\t  "~getExistingVariableNames(type).join("\n\t  ")
235             );
236         }
237         return ret;
238     }
239 
240 
241     public void addVarLayout(ShaderVariablesLayout layout)
242     {
243         ErrorHandler.assertLazyExit((layout.name in layouts) is null, "Shader: VariablesLayout '"~layout.name~"' is already defined");
244         if(defaultLayout is null)
245             defaultLayout = layout;
246         layouts[layout.name] = layout;
247         layout.lock(this.shaderImpl);
248         shaderImpl.createVariablesBlock(layout, shaderProgram);
249     }
250 
251     /**
252      * This creates a state in the current shader to which block will be accessed
253      * when using setVertexVar(".property"). If no default block is set ("")
254      * .property will always access the first block defined
255      * Params:
256      *   blockName = Which block will be accessed with .property
257      */
258     public void setDefaultBlock(string blockName){defaultLayout = layouts[blockName];}
259 
260     void bind()
261     {
262         static if(UseDelayedUnbinding)
263         {
264             if(lastBoundShader is shaderProgram)
265                 return;
266             if(lastBoundShader !is null)
267                 shaderImpl.unbind(lastBoundShader);
268             lastBoundShader = shaderProgram;
269         }
270         shaderImpl.bind(shaderProgram);
271     }
272     void unbind()
273     {
274         static if(UseDelayedUnbinding)
275             return;
276         shaderImpl.unbind(shaderProgram);
277     }
278     void setBlending(HipBlendFunction src, HipBlendFunction dest, HipBlendEquation eq)
279     {
280         shaderImpl.setBlending(shaderProgram, src, dest, eq);
281     }
282 
283     auto opDispatch(string member)()
284     {
285         static if(member == "useLayout")
286         {
287             isUseCall = true;
288             return this;
289         }
290         else
291         {
292             if(isUseCall)
293             {
294                 setDefaultBlock(member);
295                 isUseCall = false;
296                 ShaderVar s;
297                 return s;
298             }
299             return *defaultLayout.variables[member].sVar;
300         }
301     }
302     auto opDispatch(string member, T)(T value)
303     {
304         if(!defaultLayout.variables[member].sVar.set(value, false))
305         {
306             ErrorHandler.assertExit(false, "Invalid value of type "~
307             T.stringof~" passed to "~defaultLayout.name~"."~member);
308         }
309     }
310 
311     void sendVars()
312     {
313         foreach(string key, ShaderVariablesLayout value; layouts)
314         {
315             if(!value.isDirty)
316                 continue;
317             foreach(ref ShaderVarLayout varLayout; value.variables)
318             {
319                 if(varLayout.sVar.isDirty)
320                 {
321                     if(varLayout.sVar.type == UniformType.floating3x3)
322                         varLayout.sVar.set(HipRenderer.getMatrix(varLayout.sVar.get!Matrix3), false);
323                     else if(varLayout.sVar.type == UniformType.floating4x4)
324                         varLayout.sVar.set(HipRenderer.getMatrix(varLayout.sVar.get!Matrix4), false);
325                     if(varLayout.sVar.usesMaxTextures)
326                         varLayout.sVar.set(HipRenderer.getMaxSupportedShaderTextures(), true);
327                 }
328             }
329         }
330         shaderImpl.sendVars(shaderProgram, layouts);
331     }
332 
333     /**
334     *  Bind array of textures.
335     *   - This is handled a little different than simply blindly binding *  to each slot.
336     *   Since OpenGL, Direct3D and Metal handles that differently, a more general solution is required.
337     */
338     void bindArrayOfTextures(IHipTexture[] textures, string varName)
339     {
340         shaderImpl.bindArrayOfTextures(shaderProgram, textures, varName);
341     }
342 
343 
344     protected void deleteShaders()
345     {
346         shaderImpl.deleteShader(&fragmentShader);
347         shaderImpl.deleteShader(&vertexShader);
348     }
349 
350     bool reload()
351     {
352         vertexShader = shaderImpl.createVertexShader();
353         fragmentShader = shaderImpl.createFragmentShader();
354         shaderProgram = shaderImpl.createShaderProgram();
355 
356         return loadShaders(internalVertexSource, internalFragmentSource) == ShaderStatus.SUCCESS;
357     }
358 
359     void onRenderFrameEnd()
360     {
361         shaderImpl.onRenderFrameEnd(shaderProgram);
362     }
363 
364 }